﻿using System;
using System.Collections;
using UnityEngine;

namespace Com.LuisPedroFonseca.ProCamera2D
{
    abstract public class BaseTrigger : BasePC2D
    {
        public Action OnEnteredTrigger;
        public Action OnExitedTrigger;

        [TooltipAttribute("Every X seconds detect collision. Smaller intervals are more precise but also require more processing.")]
        public float UpdateInterval = .1f;

        public TriggerShape TriggerShape;

        [TooltipAttribute("If enabled, use the targets mid point to know when inside/outside the trigger.")]
        public bool UseTargetsMidPoint = true;
        [TooltipAttribute("If UseTargetsMidPoint is disabled, use this transform to know when inside/outside the trigger.")]
        public Transform TriggerTarget;

        protected float _exclusiveInfluencePercentage;

        Coroutine _testTriggerRoutine;

        protected bool _insideTrigger;
        protected Vector2 _vectorFromPointToCenter;

        protected int _instanceID;

        bool _triggerEnabled;

        override protected void Awake()
        {
            base.Awake();

            if (ProCamera2D == null)
                return;

            _instanceID = GetInstanceID();

            // Small random time offset to avoid having all the triggers calculatations on the same frame
            UpdateInterval += UnityEngine.Random.Range(-.02f, .02f);

            // Start update routine
            Toggle(true);
        }

        override protected void OnEnable()
        {
            base.OnEnable();

            if(_triggerEnabled)
                Toggle(true);
        }

        override protected void OnDisable()
        {
            base.OnDisable();

            _testTriggerRoutine = null;
        }

        /// <summary>Manually enable or disable the trigger</summary>
        /// <param name="value">If true it will enable the trigger. If false it will disable it.</param>
        public void Toggle(bool value)
        {
            if (value)
            {
                if (_testTriggerRoutine == null)
                    _testTriggerRoutine = StartCoroutine(TestTriggerRoutine());

                _triggerEnabled = true;
            }
            else
            {
                if (_testTriggerRoutine != null)
                {
                    StopCoroutine(_testTriggerRoutine);
                    _testTriggerRoutine = null;
                }

                _triggerEnabled = false;
            }
        }

        /// <summary>Manually force the trigger to test if the target(s) is inside it</summary>
        public void TestTrigger()
        {
            var triggerPos = ProCamera2D.TargetsMidPoint;
            if (!UseTargetsMidPoint && TriggerTarget != null)
                triggerPos = TriggerTarget.position;

            if (TriggerShape == TriggerShape.RECTANGLE &&
                Utils.IsInsideRectangle(
                    Vector3H(_transform.position), 
                    Vector3V(_transform.position), 
                    Vector3H(_transform.localScale), 
                    Vector3V(_transform.localScale), 
                    Vector3H(triggerPos), 
                    Vector3V(triggerPos)))
            {
                if (!_insideTrigger)
                    EnteredTrigger();
            }
            else if (TriggerShape == TriggerShape.CIRCLE &&
                Utils.IsInsideCircle(
                    Vector3H(_transform.position), 
                    Vector3V(_transform.position), 
                    (Vector3H(_transform.localScale) + Vector3V(_transform.localScale)) * .25f, 
                    Vector3H(triggerPos), 
                    Vector3V(triggerPos)))
            {
                if (!_insideTrigger)
                    EnteredTrigger();
            }
            else
            {
                if (_insideTrigger)
                    ExitedTrigger();
            }
        }

        protected virtual void EnteredTrigger()
        {
            _insideTrigger = true;

            if (OnEnteredTrigger != null)
                OnEnteredTrigger();
        }

        protected virtual void ExitedTrigger()
        {
            _insideTrigger = false;

            if (OnExitedTrigger != null)
                OnExitedTrigger();
        }

        IEnumerator TestTriggerRoutine()
        {
            yield return new WaitForEndOfFrame();

            var waitForSeconds = new WaitForSeconds(UpdateInterval);
            while (true)
            {
                TestTrigger();
                yield return waitForSeconds;
            }
        }

        protected float GetDistanceToCenterPercentage(Vector2 point)
        {
            _vectorFromPointToCenter = point - new Vector2(Vector3H(_transform.position), Vector3V(_transform.position));
            if (TriggerShape == TriggerShape.RECTANGLE)
            {
            	var distancePercentageH = Vector3H(_vectorFromPointToCenter) / (Vector3H(_transform.localScale) * .5f);
            	var distancePercentageV = Vector3V(_vectorFromPointToCenter) / (Vector3V(_transform.localScale) * .5f);
            	var distancePercentage = (Mathf.Max(Mathf.Abs(distancePercentageH), Mathf.Abs(distancePercentageV))).Remap(_exclusiveInfluencePercentage, 1, 0, 1);
            	return distancePercentage;
            }
            else
            {
            	var distancePercentage = (_vectorFromPointToCenter.magnitude / ((Vector3H(_transform.localScale) + Vector3V(_transform.localScale)) * .25f)).Remap(_exclusiveInfluencePercentage, 1, 0, 1);
            	return distancePercentage;
            }
        }

        #if UNITY_EDITOR
        override protected void DrawGizmos()
        {
            float cameraDepthOffset = Vector3D(ProCamera2D.transform.localPosition) + Mathf.Abs(Vector3D(ProCamera2D.transform.localPosition)) * Vector3D(ProCamera2D.transform.forward);
            var cameraCenter = VectorHVD(Vector3H(transform.position), Vector3V(transform.position), cameraDepthOffset);

            Gizmos.color = EditorPrefsX.GetColor(PrefsData.TriggerShapeColorKey, PrefsData.TriggerShapeColorValue);

            if (TriggerShape == TriggerShape.RECTANGLE)
            {
                Gizmos.DrawWireCube(cameraCenter, VectorHVD(Vector3H(transform.localScale), Vector3V(transform.localScale), 0f));

                if(_exclusiveInfluencePercentage > 0)
                    Gizmos.DrawWireCube(cameraCenter, VectorHVD(Vector3H(transform.localScale) * _exclusiveInfluencePercentage, Vector3V(transform.localScale) * _exclusiveInfluencePercentage, 0f));
            }
            else
            {
                var axis = Vector3.zero;
                switch (ProCamera2D.Axis)
                {
                    case MovementAxis.XY:
                        axis = new Vector3(1f, 1f, 0f);
                        break;

                    case MovementAxis.XZ:
                        axis = new Vector3(1f, 0f, 1f);
                        break;

                    case MovementAxis.YZ:
                        axis = new Vector3(0f, 1f, 1f);
                        break;
                }

                Gizmos.matrix = Matrix4x4.TRS(cameraCenter, Quaternion.identity, axis);
                Gizmos.DrawWireSphere(Vector3.zero, ((Vector3H(transform.localScale) + Vector3V(transform.localScale)) * .25f));

                if (_exclusiveInfluencePercentage > 0)
                    Gizmos.DrawWireSphere(Vector3.zero, ((Vector3H(transform.localScale) + Vector3V(transform.localScale)) * .25f) * _exclusiveInfluencePercentage);

                Gizmos.matrix = Matrix4x4.identity;
            }
        }
        #endif
    }

    public enum TriggerShape
    {
        CIRCLE,
        RECTANGLE
    }
}